什么是 generator ?
可以暂停( pause )和唤醒( resume )的函数。
实现一个迭代器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function* gen() { for (let a of [1, 2, 3]) { yield a + 1; } return 5; } for (let b of gen()) { print(JSON.stringify(b)); } let it = gen(); print(JSON.stringify(it.next())); print(JSON.stringify(it.next())); print(JSON.stringify(it.next())); print(JSON.stringify(it.next())); print(JSON.stringify([...gen()]));
|
创建 generator 的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| function* genFunc() { ··· } let genObj = genFunc(); const genFunc = function* () { ··· }; let genObj = genFunc(); let obj = { * generatorMethod() { ··· } }; let genObj = obj.generatorMethod(); class MyClass { * generatorMethod() { ··· } } let myInst = new MyClass(); let genObj = myInst.generatorMethod();
|
generator 嵌套: yield*
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function* gen1() { yield 2; yield 3; return 'result of gen1'; } function* gen2() { yield 1; print(JSON.stringify(yield* gen1())); yield 4; } print(JSON.stringify([...gen2()])); function* gen3() { yield 1; yield* [2, 3]; yield 4; } print(JSON.stringify([...gen3()]));
|
next 传值
1 2 3 4 5 6 7 8 9 10
| function* gen1() { print(JSON.stringify(yield)); } let it = gen1(); print(JSON.stringify(it.next())); print(JSON.stringify(it.next('outer value')));
|
return()
外部终止 generator
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function* gen1() { print('a'); yield 1; print('b'); yield 2; print('c'); } let it = gen1(); print(JSON.stringify(it.next())); print(JSON.stringify(it.return('result')));
|
throw()
抛出异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function* gen() { try { print('Started'); yield; } catch (error) { print('Caught: ' + error.message); } return 'return result'; } let it = gen(); print(JSON.stringify(it.next())); print(JSON.stringify(it.throw(new Error('error'))));
|
很有有趣也有用的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| function* fn1(iterable) { for (let x of iterable) { if (x === 0) { continue; } yield x; } } function* fn2(iterable) { for (let x of iterable) { yield x + 1; } } function* fn3(iterable) { for (let x of iterable) { yield x / 2; } } let newArr = [...fn3(fn2(fn1([1, 2, 3])))]; print(JSON.stringify(newArr));
|
generator 类图
规范里面有一张很大的图,有点复杂。所以,看一张小图:
说明:
- 空心箭头表示两个对象的继承关系。换句话说,从 x 指向 y 的箭头意味着
Object.getPrototypeOf(x) === y
。
- 圆括号表示当前被包起来的对象是存在的,但是不能通过全局变量来访问。
- 带有
instanceof
字眼的箭头如果从 x 指向 y ,就表明 x instanceof y
。
o instanceof C
实际上就相当于 C.prototype.isPrototypeOf(o)
- 带有
prototype
字眼的箭头如果从 x 指向 y ,就表明 x.prototype === y
。
此图看完可能没有直观的感受,看两个例子先。
第一个, generator 函数表现得很像一个构造函数,因为通过 new
调用和直接调用,两者的效果是一样的,都返回 generator 对象,如下所示:
1 2 3 4 5 6 7
| > function* g() {} > g.prototype.hello = function () { return 'hi!'}; > let obj = g(); > obj instanceof g true > obj.hello() 'hi!'
|
第二个,如果想给所有的 generator 对象添加一个方法,就可以放在 (Generator).prototype
上面,如下所示:
1 2 3 4 5
| > let Generator_prototype = Object.getPrototypeOf(function* () {}).prototype; > Generator_prototype.hello = function () { return 'hi!'}; > let generatorObject = (function* () {})(); > generatorObject.hello() 'hi!'
|
generator 内部的 this
是有一些猫腻的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function* gen1() { 'use strict'; yield this; } let [functionThis] = gen1(); console.log(functionThis); let obj = { method: gen1 }; let [methodThis] = obj.method(); console.log(methodThis === obj); function* gen2() { console.log(this); } new gen2();
|
一个简单的类似于 tj co 库的东西
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| executeGeneratorFn(function* () { var result = yield request.bind(null, 'http://www.baidu.com', {userId: 1}); console.log(result); }); executeGeneratorFn(function* () { var userList = []; for (var param of [{userId: 1}, {userId: 2}]) { userList.push(yield request.bind(null, 'http://www.baidu.com', param)); } console.log(userList); }); function request(url, params, callback) { setTimeout(() => callback('request result: ' + Math.random()), 3000); } function executeGeneratorFn(genFn, callback) { var iterator = genFn(); next(); function next() { try { execute(iterator.next(arguments)); } catch (e) { callback instanceof Function && callback(e); } } function execute(nextValue) { if (!nextValue.done) { nextValue.value(next); } else { callback instanceof Function && callback(null, nextValue.value); } } }
|